//
//  SwiftLintCommands.swift
//  JKTool
//
//  Created by 姜奎 on 2024/4/16.
//

import Foundation

enum LintType: String {
    case swiftlint,oclint,infer

    init(rawValue: String) {
        switch rawValue {
        case LintType.swiftlint.rawValue:
            self = .swiftlint
        case LintType.oclint.rawValue:
            self = .oclint
        case LintType.infer.rawValue:
            self = .infer
        default:
            self = .swiftlint
        }
    }
}

extension ShellOutCommand {
    /// IOS framework cache
    static func xcpretty(_ path:String, base command: String? = "xcpretty") -> ShellOutCommand {
        
        let shell = "export LANG=en_US.UTF-8 && \(command ?? "xcpretty") -r json-compilation-database -o \(path)/compile_commands.json < \(path)/xcodebuild.log > /dev/null"
        return ShellOutCommand(string: shell)
    }
    
    static func ivfsstatcache(_ path:String) -> ShellOutCommand {
        let shell = "sed -i '' 's/-ivfsstatcache[^\\\"]*\\.sdkstatcache//g' \"\(path)/compile_commands.json\""
        return ShellOutCommand(string: shell)
    }
    
    static func oclint(_ path:String, base command: String? = "oclint-json-compilation-database") -> ShellOutCommand {
        let shell = "\(command ?? "oclint-json-compilation-database") -p \(path) -e Pods -- -report-type xml >> \(path)/oclint-report.xml"
        return ShellOutCommand(string: shell)
    }
    
    static func swiftlint(_ path:String, base command: String? = "swiftlint") -> ShellOutCommand {
        let shell = "\(command ?? "swiftlint") lint > \(path)/swiftlint.txt"
        return ShellOutCommand(string: shell)
    }
    
    static func infer(_ path:String, base command: String? = "infer") -> ShellOutCommand {
        let shell = "\(command ?? "infer") run --skip-analysis-in-path Pods --keep-going --compilation-database \(path)/compile_commands.json --results-dir \(path)/infer-out"
        return ShellOutCommand(string: shell)
    }
    
    static func sonarScanner(_ path:String, lints:[LintType], hostUrl:String?, buildNumber:String?,projectVersion:String?,login: String?,projectKey: String?,projectName: String?, base command: String? = "sonar-scanner") -> ShellOutCommand {
        var shell = "\(command ?? "sonar-scanner") -Dsonar.sources=. -Dsonar.working.directory=\(path)/scannerwork"
        if let hostUrl = hostUrl {
            shell.connected(spaceCommand: "-Dsonar.host.url=\(hostUrl)")
        }
        if let buildNumber = buildNumber {
            shell.connected(spaceCommand: "-Dsonar.analysis.buildNumber=\(buildNumber)")
        }
        if let projectVersion = projectVersion {
            shell.connected(spaceCommand: "-Dsonar.projectVersion=\(projectVersion)")
        }
        if let login = login {
            shell.connected(spaceCommand: "-Dsonar.login=\(login)")
        }
        if let projectKey = projectKey {
            shell.connected(spaceCommand: "-Dsonar.projectKey=\(projectKey)")
        }
        if let projectName = projectName {
            shell.connected(spaceCommand: "-Dsonar.projectName=\(projectName)")
        }
        
        for lint in lints {
            shell.connected(spaceCommand: lint.report(path))
        }
        
        return ShellOutCommand(string: shell)
    }
}

extension LintType {
    func report(_ path:String) -> String {
        switch self {
        case .swiftlint:
            return "-Dsonar.swift.swiftlint.report=\(path)/swiftlint.txt"
        case .oclint:
            return "-Dsonar.swift.oclint.report=\(path)/oclint-report.xml"
        case .infer:
            return "-Dsonar.swift.infer.report=\(path)/infer-out/report.json"
        }
    }
    
    func configs(_ path:String?) -> ProjectConfigModel? {
        
        if let path = path,
           let data = try? Data(contentsOf: URL(fileURLWithPath: path)),
           let configs = try? JSONDecoder().decode(ProjectConfigModel.self, from: data) {
            return configs
        }
        return nil
    }
    
    func swiftLintScan(project: Project, scheme:String, configuration: String, sdk: String, isSimulators: Bool?, configPath: String?) {
        let date = Date.init().timeIntervalSince1970
    
        _ = try? shellOut(to: .createFolder(path: project.buildPath))
        let configP = configPath?.convertRelativePath(absolutPath: project.directoryPath)
        let configs = configs(configP)
        let swiftlint = ShellOutCommand.swiftlint(project.buildPath, base: configs?.sonarLintConfig?.swiftlintCommand)
        
        let swiftlintConfigPath = project.directoryPath + "/.swiftlint.yml"
        var needCopySwiftlintConfigPConfig = false
        if FileManager().fileExists(atPath: swiftlintConfigPath) == false,
           let configP = configP,
           let configs = configs,
           let swiftlintConfigP = configs.sonarLintConfig?.swiftlintConfigPath?.convertRelativePath(absolutPath: configP),
           FileManager().fileExists(atPath: swiftlintConfigP) {
            needCopySwiftlintConfigPConfig = true
            po(tip:"【\(scheme)】swiftlint 读取配置")
            _ = try? FileManager.default.copyItem(atPath: swiftlintConfigP, toPath: swiftlintConfigPath)
        }
        po(tip:"【\(scheme)】swiftlint 开始")
        _ = try? shellOut(to:swiftlint, at: project.directoryPath)
        
        if needCopySwiftlintConfigPConfig {
            _ = try? FileManager.default.removeItem(atPath: swiftlintConfigPath)
        }
        po(tip:"【\(scheme)】swiftlint 结束[\(GlobalConstants.duration(to: date) + " s")]")
    }
    
    func ocLintScan(project: Project, scheme:String, configuration: String, sdk: String, isSimulators: Bool?, configPath: String?) throws {
        let date = Date.init().timeIntervalSince1970
        _ = try? shellOut(to: .createFolder(path: project.buildPath))

        var contentsPath: String? = nil
        var contentsData: Data? = nil

        if let path = project.workSpaceEntrance {
            po(tip:"【\(scheme)】oclint 尝试修改contents.xcworkspacedata文件")
            contentsPath = project.directoryPath + "/" + path + "/contents.xcworkspacedata"
            contentsData = try? Data(contentsOf: URL(fileURLWithPath: contentsPath!))
            let contents = contentsData?.toString()

            let repContents = contents?.replacingOccurrences(of: "<!-- ", with: "").replacingOccurrences(of: " -->", with: "")

            let repContentsData = repContents?.toData()

            try? repContentsData?.write(to: URL(fileURLWithPath: contentsPath!), options: .atomicWrite)
        }

        let build = ShellOutCommand.build(scheme: scheme, projectType: project.projectType, configuration: configuration, sdk: sdk, isSimulators: isSimulators)
        po(tip:"【\(scheme)】oclint 生成xcodebuild.log文件")
        let xcodeBuildLog = try shellOut(to:build, at: project.directoryPath)
        
        let data = xcodeBuildLog.data(using: .utf8)
        let path = project.buildPath.appending("/xcodebuild.log")
        try data?.write(to: URL(fileURLWithPath: path), options: .atomicWrite)
        
        let configP = configPath?.convertRelativePath(absolutPath: project.directoryPath)
        let configs = configs(configP)
        
        let xcpretty = ShellOutCommand.xcpretty(project.buildPath, base: configs?.sonarLintConfig?.xcprettyCommand)
        po(tip:"【\(scheme)】oclint 生成compile_commands.json文件")
        try shellOut(to:xcpretty, at: project.directoryPath)
        
        let ivfsstatcache = ShellOutCommand.ivfsstatcache(project.buildPath)
        po(tip:"【\(scheme)】oclint 优化compile_commands.json文件")
        try shellOut(to:ivfsstatcache, at: project.directoryPath)
        
        let oclintConfigPath = project.directoryPath + "/.oclint"
        var needCopyOclintConfig = false
        
        if FileManager().fileExists(atPath: oclintConfigPath) == false,
           let configP = configP,
           let configs = configs,
           let oclintConfigP = configs.sonarLintConfig?.oclintConfigPath?.convertRelativePath(absolutPath: configP),
           FileManager().fileExists(atPath: oclintConfigP) {
            needCopyOclintConfig = true
            po(tip:"【\(scheme)】oclint 读取配置")
            _ = try? FileManager.default.copyItem(atPath: oclintConfigP, toPath: oclintConfigPath)
        }
        
        let oclint = ShellOutCommand.oclint(project.buildPath, base: configs?.sonarLintConfig?.oclintCommand)
        po(tip:"【\(scheme)】oclint 开始")
        _ = try? shellOut(to:oclint, at: project.directoryPath)

        if let contentsData = contentsData,
           let contentsPath = contentsPath {
            try? contentsData.write(to: URL(fileURLWithPath: contentsPath), options: .atomicWrite)
        }

        if needCopyOclintConfig {
            _ = try? FileManager.default.removeItem(atPath: oclintConfigPath)
        }
        po(tip:"【\(scheme)】oclint 结束[\(GlobalConstants.duration(to: date) + " s")]")
    }
    
    func inferScan(project: Project, scheme:String, configuration: String, sdk: String, isSimulators: Bool?, configPath: String?) throws {
        let date = Date.init().timeIntervalSince1970
        
        _ = try? shellOut(to: .createFolder(path: project.buildPath))

        var contentsPath: String? = nil
        var contentsData: Data? = nil

        if let path = project.workSpaceEntrance {
            po(tip:"【\(scheme)】oclint 尝试修改contents.xcworkspacedata文件")
            contentsPath = project.directoryPath + "/" + path + "/contents.xcworkspacedata"
            contentsData = try? Data(contentsOf: URL(fileURLWithPath: contentsPath!))
            let contents = contentsData?.toString()

            let repContents = contents?.replacingOccurrences(of: "<!-- ", with: "").replacingOccurrences(of: " -->", with: "")

            let repContentsData = repContents?.toData()

            try? repContentsData?.write(to: URL(fileURLWithPath: contentsPath!), options: .atomicWrite)
        }

        let build = ShellOutCommand.build(scheme: scheme, projectType: project.projectType, configuration: configuration, sdk: sdk, isSimulators: isSimulators)
        po(tip:"【\(scheme)】infer 生成xcodebuild.log文件")
        let xcodeBuildLog = try shellOut(to:build, at: project.directoryPath)
        
        let data = xcodeBuildLog.data(using: .utf8)
        let path = project.buildPath.appending("/xcodebuild.log")
        try data?.write(to: URL(fileURLWithPath: path), options: .atomicWrite)
        
        let configP = configPath?.convertRelativePath(absolutPath: project.directoryPath)
        let configs = configs(configP)
        
        let xcpretty = ShellOutCommand.xcpretty(project.buildPath, base: configs?.sonarLintConfig?.xcprettyCommand)
        
        po(tip:"【\(scheme)】infer 生成compile_commands.json文件")
        try shellOut(to:xcpretty, at: project.directoryPath)
        
        let ivfsstatcache = ShellOutCommand.ivfsstatcache(project.buildPath)
        po(tip:"【\(scheme)】infer 优化compile_commands.json文件")
        try shellOut(to:ivfsstatcache, at: project.directoryPath)
//        
        
        let inferConfigPath = project.directoryPath + "/.inferconfig"
        var needCopyInferConfig = false
        if FileManager().fileExists(atPath: inferConfigPath) == false,
           let configP = configP,
           let configs = configs,
           let inferConfigP = configs.sonarLintConfig?.inferConfigPath?.convertRelativePath(absolutPath: configP),
           FileManager().fileExists(atPath: inferConfigP) {
            needCopyInferConfig = true
            po(tip:"【\(scheme)】infer 读取配置")
            _ = try? FileManager.default.copyItem(atPath: inferConfigP, toPath: inferConfigPath)
        }
        
        let infer = ShellOutCommand.infer(project.buildPath, base: configs?.sonarLintConfig?.inferCommand)
        po(tip:"【\(scheme)】infer 开始")
        _ = try shellOut(to:infer, at: project.directoryPath)

        if let contentsData = contentsData,
           let contentsPath = contentsPath {
            try? contentsData.write(to: URL(fileURLWithPath: contentsPath), options: .atomicWrite)
        }

        if needCopyInferConfig {
            _ = try? FileManager.default.removeItem(atPath: inferConfigPath)
        }
        
        po(tip:"【\(scheme)】infer 结束[\(GlobalConstants.duration(to: date) + " s")]")
    }
    
    func scan(project: Project, scheme:String, configuration: String, sdk: String, isSimulators: Bool?, configPath: String?) throws {
        switch self {
        case .swiftlint:
            swiftLintScan(project: project, scheme: scheme, configuration: configuration, sdk: sdk, isSimulators: isSimulators, configPath: configPath)
        case .oclint:
            try ocLintScan(project: project, scheme: scheme, configuration: configuration, sdk: sdk, isSimulators: isSimulators, configPath: configPath)
        case .infer:
            try inferScan(project: project, scheme: scheme, configuration: configuration, sdk: sdk, isSimulators: isSimulators, configPath: configPath)
        }
    }
}
